<?php
#header('content-type: text/plain');
//class ORM {
//	
//	function __construct($dbname, $dbhost = 'localhost', $dbuser = 'root', $dbpass = ''){
//		
//	}
//	
//	function __destruct(){
//		
//	}
//	
//	
//};

/*

Note: if two tables have the same FK name, the there could be an issue

Note: this:that .... ":that" can be implied
Catch circular dependencies?

*/


#DEBUG BEGIN###############################
//$dh = opendir('./classes/');
//while($file = readdir($dh)){
//	if(is_file("./classes/$file") && stripos($file, '.class.php') !== false){
//		unlink("./classes/$file");
//	}
//}
//closedir($dh);
@unlink('./classes/OpenScriptures.db.class.php');
#DEBUG END###############################

function orm_phpize($identifier){
	$identifier = preg_replace('/[^a-z0-9_]+/', '_', $identifier);
	return preg_replace_callback('/(^\w)|(_\w)/', create_function('$match', 'return ltrim(strtoupper($match[0]), "_"); '), strtolower($identifier));
}


function orm_init($dbname, $classpath = '.', $dbhost = 'localhost', $dbuser = 'root', $dbpass = ''){
	if(!file_exists($classpath))
		throw new Exception("Classpath directory '$classpath' does not exist.");

	//Skip init if files already exist
	$dbname_nice = orm_phpize($dbname);
	echo $dbname_nice;
	if(file_exists("$classpath/$dbname_nice.db.class.php"))
		return false;
	
	$fdb = @fopen("$classpath/$dbname_nice.db.class.php", 'w');
	if(!$fdb)
		throw new Exception("Unable to open $classpath/$dbname_nice.db.class.php for writing");

	$ormpath = dirname(__FILE__);
	$dbsuperclass =
<<<BP
<?php
/*This file is automatically generated; do not modify*/
#require_once('orm.php');
class OpenScriptures_DB extends ORM {
	static protected \$dbhost = '$dbhost';
	static protected \$dbname = '$dbname';
	static private \$dbuser = '$dbuser';
	static private \$dbpass = '$dbpass';
	static protected \$dbconn = null;
	static protected function connect(){
		if(self::\$dbconn)
			return;
		self::\$dbconn = mysql_connect(self::\$dbhost, self::\$dbname, self::\$dbuser, self::\$dbpass);
		if(!self::\$dbconn)
			throw new Exception("Unable to connect to database " . self::\$dbname . " on " . self::\$dbhost);
	}
};
?>
BP;
	//$dbsuperclass = str_replace('__dbhost__', $dbhost, $dbsuperclass);
	//$dbsuperclass = str_replace('__dbname__', $dbname, $dbsuperclass);
	//$dbsuperclass = str_replace('__dbuser__', $dbuser, $dbsuperclass);
	//$dbsuperclass = str_replace('__dbpass__', $dbpass, $dbsuperclass);
	#fputs($fdb, "<?php\n/*This file is automatically generated; do not modify*/\n");
	//fputs($fdb, "class " . $dbname_nice . "_DB extends ORM {\n");
	//fputs($fdb, "	static protected \$dbhost = '$dbhost';\n");
	//fputs($fdb, "	static protected \$dbname = '$dbname';\n");
	//fputs($fdb, "	static protected \$dbuser = '$dbuser';\n");
	//fputs($fdb, "	static protected \$dbpass = '$dbpass';\n");
	//fputs($fdb, "	static protected \$dbconn = null;\n");
	//fputs($fdb, "};\n?".">");
	fputs($fdb, $dbsuperclass);
	fclose($fdb);
	
	//Compose introdpection query
	$_dbname = mysql_escape_string($dbname);
	$sql =
<<<SQL
		SELECT
			c.TABLE_NAME,
			c.COLUMN_NAME,
			c.IS_NULLABLE,
			c.EXTRA,
			c.DATA_TYPE,
			c.CHARACTER_MAXIMUM_LENGTH,
			/*c.CHARACTER_OCTET_LENGTH,*/
			c.COLUMN_DEFAULT,
			c.COLUMN_KEY,
		
			kcu.CONSTRAINT_NAME,
			kcu.REFERENCED_TABLE_NAME,
			kcu.REFERENCED_COLUMN_NAME,
			
			tc.CONSTRAINT_TYPE,
			c.COLUMN_COMMENT
		FROM
			COLUMNS c
			LEFT JOIN (
				KEY_COLUMN_USAGE kcu,
				TABLE_CONSTRAINTS tc
			)
			ON (
				kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA AND tc.CONSTRAINT_SCHEMA  = c.TABLE_SCHEMA AND
				kcu.CONSTRAINT_NAME   = tc.CONSTRAINT_NAME AND
				kcu.TABLE_NAME        = tc.TABLE_NAME AND
				c.TABLE_NAME          = kcu.TABLE_NAME AND
				c.COLUMN_NAME         = kcu.COLUMN_NAME
			)
		WHERE
			c.TABLE_SCHEMA = "$_dbname"
		ORDER BY
			c.TABLE_NAME,
			c.ORDINAL_POSITION;
SQL;
	
	//Connect to the database
	$link = @mysql_connect($dbhost, $dbuser, $dbpass);
	if(!$link)
		throw new Exception(mysql_error(), mysql_errno());
	@mysql_select_db('INFORMATION_SCHEMA');
	ORM::assertMySQLError($link);
	$introspect = @mysql_query($sql);
	ORM::assertMySQLError($link);
	
	
	
	$tables = array();
	while($r = mysql_fetch_object($introspect)){
		//Setup this table
		if(!isset($tables[$r->TABLE_NAME])){
			$tables[$r->TABLE_NAME] = array();
			$tables[$r->TABLE_NAME]['columns'] = array();
		}
		
		//Set up new column
		if(empty($tables[$r->TABLE_NAME]['columns'][$r->COLUMN_NAME])){
			$tables[$r->TABLE_NAME]['columns'][$r->COLUMN_NAME] = array();
			$tables[$r->TABLE_NAME]['columns'][$r->COLUMN_NAME]['constraints'] = array();
		}
		
		//Copy column properties
		$c = &$tables[$r->TABLE_NAME]['columns'][$r->COLUMN_NAME];
		$c['DATA_TYPE'] = $r->DATA_TYPE;
		$c['IS_NULLABLE'] = $r->IS_NULLABLE == 'NO' ? false : true;
		if(!empty($r->CHARACTER_MAXIMUM_LENGTH))
			$c['CHARACTER_MAXIMUM_LENGTH'] = (int)$r->CHARACTER_MAXIMUM_LENGTH;
		$c['COLUMN_DEFAULT'] = $r->COLUMN_DEFAULT;
		if(!empty($r->COLUMN_KEY))
			$c['COLUMN_KEY'] = $r->COLUMN_KEY;
		if(!empty($r->COLUMN_COMMENT))
			$c['COLUMN_COMMENT'] = $r->COLUMN_COMMENT;
		
		//Add constraint details
		if(!empty($r->CONSTRAINT_NAME) || !empty($r->REFERENCED_TABLE_NAME) || !empty($r->REFERENCED_COLUMN_NAME) || !empty($r->CONSTRAINT_TYPE)){
			$constraint = array();
			
			#if(!empty($r->CONSTRAINT_NAME))
			#	$constraint['CONSTRAINT_NAME'] = $r->CONSTRAINT_NAME;
			if(!empty($r->REFERENCED_TABLE_NAME))
				$constraint['REFERENCED_TABLE_NAME'] = $r->REFERENCED_TABLE_NAME;
			if(!empty($r->REFERENCED_COLUMN_NAME))
				$constraint['REFERENCED_COLUMN_NAME'] = $r->REFERENCED_COLUMN_NAME;
			if(!empty($r->CONSTRAINT_TYPE))
				$constraint['CONSTRAINT_TYPE'] = $r->CONSTRAINT_TYPE;
			
			//array_push($c['constraints'], $constraint);
			$c['constraints'][$r->CONSTRAINT_NAME] = $constraint;
		}
	}
	file_put_contents('schema-data.txt', var_export($tables, true));
	
	$cwd = realpath('.');
	chdir($classpath);
	foreach($tables as $table_name => $data){
		print "<h3>$table_name</h3>";
		$table_nicename = orm_phpize($table_name);
		#$table = preg_replace_callback('/(^\w)|(_\w)/', create_function('$match', 'return ltrim(strtoupper($match[0]), "_"); '), $table);
		
		$schema = str_replace("\n", "\r\n", ltrim(var_export($data['columns'], true)));
		$schema = str_replace("  ", "\t", $schema);
		$schema = preg_replace("{(?<=\r\n)}", "\t", $schema);
		
		$primaryKeys = array();
		foreach($data['columns'] as $name => $attrs){
			$isPrimary = false;
			foreach($attrs['constraints'] as $constraint){
				if($constraint['CONSTRAINT_TYPE'] == 'PRIMARY KEY'){
					//$isPrimary = true;
					$primaryKeys[] = preg_replace('{:.+}', '', $name); //TODO
				}
			}
		}
		
		$selectArgList = array();
		#foreach($primaryKeys as $pk){
		#	$selectArgList[] = "\$$pk";
		#}
		$selectArgList = join(', ', array_map(create_function('$a', 'return "\$$a";'), $primaryKeys));
		$selectByArgList = "\r\n" . join(', ', array_map(create_function('$a', "return \"\t\t\t'\$a' => \$\$a\r\n\";"), $primaryKeys)) . "\t\t";
		
		
		//NOTE: We can simply pass the arguments AND the $schema to the parent method which can then do the data-validation and all of
		//      the complicated logic. Remember also that we need to plug-in custom validation constraints, filters, and
		//      visibility constraints from the user.
		
		$class = <<<CLASS
<?php
/*This file is automatically generated; do not modify*/
require_once(dirname(__FILE__) . "/$dbname_nice.db.class.php");
class $table_nicename extends OpenScriptures_DB {
	const TABLE_NAME = "$table_name";
	static protected \$schema = $schema;
	
	function __construct(){
		parent::__construct();
	}
	
	public function select($selectArgList){
		if(!parent::\$dbconn)
			parent::connect();
		\$result = parent::rawSelectBy(parent::\$dbconn, self::TABLE_NAME, array($selectByArgList));
		
		while(\$row = mysql_fetch_assoc(\$result)){
			print_r(\$row);
		}
		
		//don't return here! We need to turn the results into objects
	}
	
	public function selectWhere(\$clause){
		if(!parent::\$dbconn)
			parent::connect();
		return parent::selectWhere(parent::\$dbconn, self::TABLE_NAME, \$clause);
	}
	
};
?>
CLASS;
		
		echo '<pre>';
		print_r($data);
		echo '</pre>';
		
		//NOTE: We should include the standard comment system!
		
		//$f = fopen("$classpath/$dbname_nice.db.class.php", 'w');
		
		file_put_contents(orm_phpize($table_name) . ".class.php", $class);
		
	}
	chdir($cwd);
	
	
	
	$u = new User();
	User::select(1);
	#echo mysql_escape_string("~\"`'");
	#echo mysql_real_escape_string("~\"`'");
	
	#echo ORM::escapeValueList(array("~\"'hkhku&^*&", 2, new DateTime('9/1/2008')));
	
	mysql_close($link);
	return true;
}

class ORM {
	#DB bookkeeping
	#static protected $conns = array();
	#static protected $objCounts = array();
	
	//protected $dbconn;
	
	#Object-table data
	protected $records = array();
	
	
	protected function __construct(){
		//if(@!$this->dbname)
		//	throw new Exception("Database name not provided");
		//
		//#Connect to the database if it hasn't been connected to yet
		//$this->connStr = $this->dbhost . ':' . $this->dbuser . ':' . $this->dbpass;
		//if(empty(self::$conns[$this->connStr])){
		//	self::$objCounts[$this->connStr] = 0;
		//	self::$conns[$this->connStr] = @mysql_connect($this->dbhost, $this->dbuser, $this->dbpass);
		//	if(mysql_errno())
		//		throw new Exception(myqsl_error(), mysql_errno());
		//	@mysql_select_db($this->dbname);
		//	if(mysql_errno())
		//		throw new Exception(myqsl_error(), mysql_errno());
		//}
		//$this->dbconn = &self::$conns[$this->connStr];
		//self::$objCounts[$this->connStr]++;
	}
	
	function __destruct(){
		#if(--self::$objCounts[$this->connStr] == 0){
		#	mysql_close($this->dbConn);
		#	$this->dbConn = null;
		#}
		
	}
	
	protected function &db_connect($dbhost, $dbuser, $dbpass, $dbname){
		$connStr = "$dbhost:$dbuser:$dbname";
		if(empty(self::$conns[$connStr])){
			self::$conns[$connStr] = @mysql_connect($dbhost, $dbuser, $dbpass);
			if(!self::$conns[$connStr])
				throw new Exception("Unable to connect to $dbuser@$dbhost");
			ORM::assertMySQLError(self::$conns[$connStr]);
			@mysql_select_db($dbname);
			ORM::assertMySQLError(self::$conns[$connStr]);
		}
		return self::$conns[$connStr];
	}
	
	
	//protected function select($dbconn, $tableName /*...*/){
	//	mysql_query("SELECT * FROM `$tableName` WHERE ");
	//	#if(!self::$dbconn)
	//	#	$dbconn
	//}
	protected function rawSelectWhere($dbconn, $tableName, $clause){
		$sql = "SELECT * FROM `$tableName` WHERE $clause";
		$result = mysql_query($sql, $dbconn);
		ORM::assertMySQLError($dbconn);
		return $result;
	}
	protected function rawSelectBy($dbconn, $tableName, array $cols){
		$conds = array();
		foreach($cols as $key => $val)
			$conds[] = "`$key` = " . self::escapeValue($val);
		return self::rawSelectWhere($dbconn, $tableName, join(' AND ', $conds));
	}
	
	
	//Static helper functions
	public static function escapeValue($value){
		if(is_string($value))
			return '"' . mysql_escape_string($value) . '"';
		if(is_null($value))
			return 'NULL';
		if(is_numeric($value))
			return $value;
		if(is_bool($value))
			return $value ? '1' : '0';
		if(is_array($value))
			throw new Exception('Cannot convert arrays to single SQL values');
		if(is_object($value)){
			if(get_class($value) == 'DateTime')
				return self::escapeValue($value->format('c'));
			
			throw new Exception('Unable to convert ' . get_class($value) . ' object');
		}
		
	}
	public static function escapeValueList(array $values){
		$items = array();
		foreach($values as $value){
			$items[] = self::escapeValue($value);
		}
		return '(' . join(',', $items) . ')';
	}
	public static function assertMySQLError($link = null){
		if(mysql_errno($link))
			throw new Exception(mysql_error($link), mysql_errno($link));
	}
};

class OpenScriptures_DB extends ORM {
	static protected $dbhost = "localhost";       #hard coded
	static protected $dbname = "open_scriptures"; #hard coded
	static protected $dbuser = "root";            #hard coded
	static protected $dbpass = "";                #hard coded
	static protected $tableName = "user";         #hard coded
	
	static protected $dbconn = null;
};



class User extends OpenScriptures_DB {
	
	
	function __construct(){
		#DB Connect
		parent::__construct();
		
		
		
	}
	
	public function select($id){
		if(!self::$dbconn)
			self::$dbconn = parent::db_connect(self::$dbhost, self::$dbuser, self::$dbpass, self::$dbname);
		$result = parent::rawSelectBy(self::$dbconn, self::$tableName, array(
			'id' => $id
		));
		
		while($row = mysql_fetch_assoc($result)){
			print_r($row);
		}
		
		//don't return here! We need to turn the results into objects
	}
	
	public function selectWhere($clause){
		if(!self::$dbconn)
			self::$dbconn = parent::db_connect(self::$dbhost, self::$dbuser, self::$dbpass, self::$dbname);
		return parent::selectWhere(self::$dbconn, self::$tableName, $clause);
	}
	
};















class DBObject {
	
	private $conn;
	private $cols;
	//colname => array(
	//	datatype => VARCHAR(23)|INT|TEXT...
	//	nullable => true|false
	//	dependency => array('table_name', 'table_row')
	//	auto_increment => true|false
	//	default_value => ""
	//  FLAGS!!!
	//	comment => "..."
	//
	//	constraints => VALIDATION CONSTRAINTS PROVIDED BY USER
	//);
	private $tableName;
	
	private $records; //this is an array of either one element or many
	// = array(
	//	array(
	//		local-name => value(s)
	//	)
	//);
	public function count(){
		return count($this->records);
	}
	public function item($i){
		if($i < 0 || $i > $this->count()-1)
			throw new Exception('Out of bounds');
		
		//This actually returns a cloned DBObject with just item $i in the $records
		$item = clone $this;
		$item->records = array($this->records[$i]);
		return $item;
		#return $this->records[$i];
	}
	
	
	function __construct($tableName){
		$this->tableName = $tableName;
		//create
	}
	//public function create(){
	//	//call constructor
	//}
	
	function __destruct(){
		
	}
	
	public static function select(/*...*/){
		
	}
	
	
	
	
	public function get($colName, $whereFilter = null){
		//returns an array if key is FK and multiple results
		
	}
	public function set($colName, $value){
		
	}
	
	public function update(){
		
	}
	//public function save(){ return $this->update(); }
	
	public function delete(){
		
	}
};



/*

$o = new Foo();
$o = new Foo(array(col1 => A[, col2 => B...]));
$o = new Foo(A, B);

$o->update();

//Note: Each of these may return arrays if multiples are matched
$o = Foo::select($pk);
$o = Foo::selectBy(array(col1 => A));
$o = Foo::selectWhere($clause); //SELECT * FROM foo WHERE ...

$o->set('last_name', "Ruter"); //can set multiple records at once
$o->update(); //commits the change(s) in the previous line


$o->delete(); //if PK set, then delete from DB
Foo::delete($pk);
Foo::deleteWhere($clause);

$o->get($colName[, $whereFilter]); //$whereFilter is function or WHERE clause which is used to filter down the FK results

echo $o->get('mother')->get('name'); //My-mom
echo $o->get('mother')->get('child'); //[o.Notsew, o.Lindsay, o.Major, o.Peuwest]
#echo $o->get('mother')->get('child')->get('name'); //to do this, we would have to return a DBObjectArray

//A class needs to know of others that depend on it, so you can do One-to-many rels? So you could do
//    $child->get('mother');
//    $mother->get('child'); //the child table would have a key "child:mother", so the mother table could add 'child' to its property list

$o->set('name', 'Weston');
$o->setName('Weston'); //?


//Force all foreign keys to have a colon separating the local name from the remote name
//Only hydrate the FK's local member when it is getted
//get() can return array if not pointed at FK... $mom->getChild()->getName() //Notsew, Lindsay, Major, Peuwest

//Can be done in JavaScript too but with getters and setters
//Datatypes can be validation constraints... where can we put other requirements?



AUTOLOAD

*/


?>